﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Reflection.Emit;
using System.Reflection;
using System.Xml;
using System.Xml.Serialization;
using GraphMLComplexType;
using System.IO;

namespace QuickGraphBuilder
{
    public static class ComplexTypeBuilder<TKey, TValue, TParentType> 
        where TParentType : IGraphMLComplexType   
    {
        //private static readonly String m_keysName = "keys";
        private static readonly String m_typeName = "GraphMLDynamicComplexType.ComplexType";
        private static readonly Type dicType = Assembly.GetAssembly(typeof(Dictionary<TKey, TValue>)).GetType("System.Collections.Generic.Dictionary`2");
        //private static FieldBuilder keys;

        private static TypeBuilder BuildComplexType(ModuleBuilder module)
        {
            TypeBuilder type = module.DefineType(
            m_typeName,
            TypeAttributes.Public,
            typeof(TParentType)
            );


            //Type GenericType = dicType.MakeGenericType(typeof(TKey), typeof(TValue));
            //keys = type.DefineField(m_keysName, GenericType, FieldAttributes.Private);
            ConstructorBuilder(type);
            //GetKeys(type);
            return type;

        }

        private static MethodBuilder ConstructorBuilder(TypeBuilder type)
        {
            System.Reflection.MethodAttributes methodAttributes =
          System.Reflection.MethodAttributes.Public
        | System.Reflection.MethodAttributes.HideBySig;
            MethodBuilder method = type.DefineMethod(".ctor", methodAttributes);
            // Preparing Reflection instances
            ConstructorInfo ctor1 = typeof(TParentType).GetConstructor(
                BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic,
                null,
                new Type[]{
            },
                null
                );
            // Setting return type
            method.SetReturnType(typeof(void));
            // Adding parameters
            ILGenerator gen = method.GetILGenerator();
            // Writing body
            gen.Emit(OpCodes.Ldarg_0);
            gen.Emit(OpCodes.Call, ctor1);
            gen.Emit(OpCodes.Nop);
            gen.Emit(OpCodes.Nop);
            gen.Emit(OpCodes.Nop);
            gen.Emit(OpCodes.Ret);

            return method;
        }

        //private static MethodBuilder GetKeys(TypeBuilder type)
        //{
        //    // Declaring method builder
        //    // Method attributes
        //    System.Reflection.MethodAttributes methodAttributes =
        //          System.Reflection.MethodAttributes.Public
        //        | System.Reflection.MethodAttributes.Virtual
        //        | System.Reflection.MethodAttributes.Final
        //        | System.Reflection.MethodAttributes.HideBySig
        //        | System.Reflection.MethodAttributes.NewSlot;
        //    MethodBuilder method = type.DefineMethod("get_Keys", methodAttributes);
        //    // Preparing Reflection instances
        //    //FieldInfo field1 = type.GetField(m_keysName, BindingFlags.Public | BindingFlags.NonPublic);
        //    Type GenericType = dicType.MakeGenericType(typeof(String), typeof(String));
        //    // Setting return type
        //    method.SetReturnType(GenericType);
        //    // Adding parameters
        //    ILGenerator gen = method.GetILGenerator();
        //    // Preparing locals
        //    LocalBuilder CS10000 = gen.DeclareLocal(GenericType);
        //    // Preparing labels
        //    Label label10 = gen.DefineLabel();
        //    // Writing body
        //    gen.Emit(OpCodes.Nop);
        //    gen.Emit(OpCodes.Ldarg_0);
        //    gen.Emit(OpCodes.Ldfld, keys);
        //    gen.Emit(OpCodes.Stloc_0);
        //    gen.Emit(OpCodes.Br_S, label10);
        //    gen.MarkLabel(label10);
        //    gen.Emit(OpCodes.Ldloc_0);
        //    gen.Emit(OpCodes.Ret);
        //    // finished
        //    return method;

        //}

        private static MethodBuilder SetterBuilder(TypeBuilder type, String PropertyName)
        {
            // Declaring method builder
            // Method attributes
            System.Reflection.MethodAttributes methodAttributes =
                  System.Reflection.MethodAttributes.Public
                | System.Reflection.MethodAttributes.HideBySig;
            MethodBuilder method = type.DefineMethod("set_" + PropertyName, methodAttributes);
            // Preparing Reflection instances
            MethodInfo method1 = typeof(MethodBase).GetMethod(
                "GetCurrentMethod",
                BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic,
                null,
                new Type[]{
            },
                null
                );
            MethodInfo method2 = typeof(MemberInfo).GetMethod(
                "get_Name" ,
                BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic,
                null,
                new Type[]{
            },
                null
                );
            MethodInfo method3 = typeof(String).GetMethod(
                "Replace",
                BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic,
                null,
                new Type[]{
            typeof(String),
            typeof(String)
            },
                null
                );
            MethodInfo GetKeys = typeof(TParentType).GetMethod(
                "get_Keys",
                BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic,
                null,
                new Type[]{
            },
                null
                );
            //FieldInfo field4 = type.GetField(m_keysName, BindingFlags.Public | BindingFlags.NonPublic);
            Type GenericType = dicType.MakeGenericType(typeof(TKey), typeof(TValue));
            MethodInfo method5 = GenericType.GetMethod(
                "ContainsKey",
                BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic,
                null,
                new Type[]{
            GenericType.GetGenericArguments()[0]
            },
                null
                );
            MethodInfo method6 = GenericType.GetMethod(
                "set_Item",
                BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic,
                null,
                new Type[]{
            GenericType.GetGenericArguments()[0],
            GenericType.GetGenericArguments()[1]
            },
                null
                );
            MethodInfo method7 = GenericType.GetMethod(
                "Add",
                BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic,
                null,
                new Type[]{
            GenericType.GetGenericArguments()[0],
            GenericType.GetGenericArguments()[1]
            },
                null
                );
            // Setting return type
            method.SetReturnType(typeof(void));
            // Adding parameters
            method.SetParameters(
                typeof(String)
                );
            // Parameter value
            ParameterBuilder value = method.DefineParameter(1, ParameterAttributes.None, "value");
            ILGenerator gen = method.GetILGenerator();
            // Preparing locals
            LocalBuilder name = gen.DeclareLocal(typeof(String));
            LocalBuilder CS40000 = gen.DeclareLocal(typeof(Boolean));
            // Preparing labels
            Label label62 = gen.DefineLabel();
            Label label80 = gen.DefineLabel();
            // Writing body
            gen.Emit(OpCodes.Nop);
            gen.Emit(OpCodes.Call, method1);
            gen.Emit(OpCodes.Callvirt, method2);
            gen.Emit(OpCodes.Ldstr, "set_");
            gen.Emit(OpCodes.Ldstr, "");
            gen.Emit(OpCodes.Callvirt, method3);
            gen.Emit(OpCodes.Stloc_0);
            gen.Emit(OpCodes.Ldarg_0);
            gen.Emit(OpCodes.Callvirt, GetKeys);
            gen.Emit(OpCodes.Ldloc_0);
            gen.Emit(OpCodes.Callvirt, method5);
            gen.Emit(OpCodes.Ldc_I4_0);
            gen.Emit(OpCodes.Ceq);
            gen.Emit(OpCodes.Stloc_1);
            gen.Emit(OpCodes.Ldloc_1);
            gen.Emit(OpCodes.Brtrue_S, label62);
            gen.Emit(OpCodes.Ldarg_0);
            gen.Emit(OpCodes.Callvirt, GetKeys);
            gen.Emit(OpCodes.Ldloc_0);
            gen.Emit(OpCodes.Ldarg_1);
            gen.Emit(OpCodes.Callvirt, method6);
            gen.Emit(OpCodes.Nop);
            gen.Emit(OpCodes.Br_S, label80);
            gen.MarkLabel(label62);
            gen.Emit(OpCodes.Ldarg_0);
            gen.Emit(OpCodes.Callvirt, GetKeys);
            gen.Emit(OpCodes.Ldstr, PropertyName);
            gen.Emit(OpCodes.Ldarg_1);
            gen.Emit(OpCodes.Callvirt, method7);
            gen.Emit(OpCodes.Nop);
            gen.MarkLabel(label80);
            gen.Emit(OpCodes.Ret);
            // finished
            return method;

        }

        private static MethodBuilder GetterBuilder(TypeBuilder type, String PropertyName)
        {
            // Declaring method builder
            // Method attributes
            System.Reflection.MethodAttributes getterMethodAttributes =
                  System.Reflection.MethodAttributes.Public
                | System.Reflection.MethodAttributes.HideBySig;
            MethodBuilder getterMethod = type.DefineMethod("get_" + PropertyName, getterMethodAttributes);
            // Preparing Reflection instances
            MethodInfo GetCurrentMethodMethod = typeof(MethodBase).GetMethod(
                "GetCurrentMethod",
                BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic,
                null,
                new Type[]{
            },
                null
                );
            MethodInfo GetKeys = typeof(TParentType).GetMethod(
                "get_Keys",
                BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic,
                null,
                new Type[]{
            },
                null
                );
            MethodInfo method2 = typeof(MemberInfo).GetMethod(
                "get_Name",
                BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic,
                null,
                new Type[]{
            },
                null
                );
            MethodInfo method3 = typeof(String).GetMethod(
                "Replace",
                BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic,
                null,
                new Type[]{
            typeof(String),
            typeof(String)
            },
                null
                );
           // FieldInfo field4 = type.GetField(m_keysName, BindingFlags.Public | BindingFlags.NonPublic);
            Type GenericType = dicType.MakeGenericType(typeof(TKey), typeof(TValue));
            MethodInfo method5 = GenericType.GetMethod(
                "ContainsKey",
                BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic,
                null,
                new Type[]{
                GenericType.GetGenericArguments()[0]
            },
                null
                );
            MethodInfo method6 = GenericType.GetMethod(
                "get_Item",
                BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic,
                null,
                new Type[]{
                GenericType.GetGenericArguments()[0]
            },
                null
                );
            FieldInfo field7 = typeof(String).GetField("Empty");
            // Setting return type
            getterMethod.SetReturnType(typeof(String));
            // Adding parameters
            ILGenerator gen = getterMethod.GetILGenerator();
            // Preparing locals
            LocalBuilder methodAttributes = gen.DeclareLocal(typeof(String));
            LocalBuilder method = gen.DeclareLocal(typeof(String));
            LocalBuilder method1 = gen.DeclareLocal(typeof(Boolean));
            // Preparing labels
            Label label61 = gen.DefineLabel();
            Label label69 = gen.DefineLabel();
            // Writing body
            gen.Emit(OpCodes.Nop);
            gen.Emit(OpCodes.Call, GetCurrentMethodMethod);
            gen.Emit(OpCodes.Callvirt, method2);
            gen.Emit(OpCodes.Ldstr, "get_");
            gen.Emit(OpCodes.Ldstr, "");
            gen.Emit(OpCodes.Callvirt, method3);
            gen.Emit(OpCodes.Stloc_0);
            gen.Emit(OpCodes.Ldarg_0);
            gen.Emit(OpCodes.Callvirt, GetKeys);
            gen.Emit(OpCodes.Ldloc_0);
            gen.Emit(OpCodes.Callvirt, method5);
            gen.Emit(OpCodes.Ldc_I4_0);
            gen.Emit(OpCodes.Ceq);
            gen.Emit(OpCodes.Stloc_2);
            gen.Emit(OpCodes.Ldloc_2);
            gen.Emit(OpCodes.Brtrue_S, label61);
            gen.Emit(OpCodes.Ldarg_0);
            gen.Emit(OpCodes.Callvirt, GetKeys);
            gen.Emit(OpCodes.Ldloc_0);
            gen.Emit(OpCodes.Callvirt, method6);
            gen.Emit(OpCodes.Stloc_1);
            gen.Emit(OpCodes.Br_S, label69);
            gen.MarkLabel(label61);
            gen.Emit(OpCodes.Ldsfld, field7);
            gen.Emit(OpCodes.Stloc_1);
            gen.Emit(OpCodes.Br_S, label69);
            gen.MarkLabel(label69);
            gen.Emit(OpCodes.Ldloc_1);
            gen.Emit(OpCodes.Ret);
            // finished
            return getterMethod;
        }

        private static void BuildProperty(TypeBuilder tb, String name)
        {
            PropertyBuilder pb = tb.DefineProperty(
            name,
            PropertyAttributes.HasDefault,
            typeof(TValue),
            null);

            MethodBuilder getter = GetterBuilder(tb, name);
            MethodBuilder setter = SetterBuilder(tb, name);

            CustomAttributeBuilder br = new CustomAttributeBuilder(typeof(XmlAttributeAttribute).GetConstructor(Type.EmptyTypes), new object[] { });
            pb.SetCustomAttribute(br);

            pb.SetGetMethod(getter);
            pb.SetSetMethod(setter);
        }

        public static Type CreateType(TextReader graphMLPath, String PathToSave)
        {
            AssemblyName assemblyName = new AssemblyName("GraphMLDynamicComplexType");
            assemblyName.Version = new Version(1, 0, 0, 0);

            DirectoryInfo infoDir = new DirectoryInfo(PathToSave);

            AssemblyBuilder assembly = AppDomain.CurrentDomain.DefineDynamicAssembly(assemblyName, AssemblyBuilderAccess.Run, infoDir.FullName);

            ModuleBuilder moduleBuilder =
            assembly.DefineDynamicModule(assemblyName.Name);
            TypeBuilder tb = BuildComplexType(moduleBuilder);

            XmlDocument doc = new XmlDocument();
            doc.Load(graphMLPath);
            XmlNodeList list = doc.GetElementsByTagName("key");
            foreach (var item in list)
            {
                XmlElement elem = ((XmlElement)item);
                try
                {
                    if (elem.Attributes["attr.type"].Value == typeof(TValue).Name.ToLower())
                    {
                        String name = elem.Attributes["attr.name"].Value;
                        PropertyInfo info = typeof(TParentType).GetProperty(name);
                        if (info == null)
                        {
                            BuildProperty(tb, name);
                        }
                    }
                }
                catch
                {
                    //TODO: add logger
                    continue;
                }
            }

            Type t = tb.CreateType();
            //assembly.Save(assemblyName.Name + ".dll");

            return t;
        }
    }
}
